package fem2.gui.pm;

import inf.vtk.algorithms.ExtractMinMaxFilter;
import inf.vtk.algorithms.LinePlotFilter;
import inf.vtk.pm.AbstractPipeline;
import inf.vtk.pm.ActorManager;
import inf.vtk.pm.DataProvider;
import inf.vtk.pm.ElementsDP;
import inf.vtk.pm.EmptyDataProvider;
import inf.vtk.pm.IDLabelsHelper;
import inf.vtk.pm.Pipeline;
import inf.vtk.pm.PreRenderable;
import inf.vtk.pm.ScalarBarFactory;
import inf.vtk.pm.SymbolsPipeline;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import vtk.vtkActor;
import vtk.vtkExtractEdges;
import vtk.vtkLookupTable;
import vtk.vtkPolyData;
import vtk.vtkPolyDataMapper;
import vtk.vtkScalarBarActor;

public class LinePlotPipeline extends AbstractPipeline implements PreRenderable {

	private class DDP extends EmptyDataProvider implements LinePlotDP {

		public String[] getAvailableFields() {
			return null;
		}

		public String getField() {
			return null;
		}

		public vtkPolyData getPolyData() {
			return new vtkPolyData();
		}

		public int getResolution() {
			return 0;
		}

		public double[] getScalarRange() {
			return null;
		}

		public void setField(String field) {
		}

		public void setResolution(int r) {
		}
	}

	private static final double MODEL_SCALE_FACTOR = 0.2;

	public static final String ACTOR_OUTLINES_WARPED = "Outlines warped";
	public static final String ACTOR_FACES = "Faces";
	public static final String ACTOR_OUTLINES = "Outlines";
	public static final String ACTOR_SCALAR_BAR = "Scalar Bar";
	public static final String ACTOR_MIN_MAX_LABELS = "MinMax Labels";
	public static final String ACTOR_MIN_MAX_POINTS = "MinMax Points";

	private LinePlotFilter linePlotLPF_ = new LinePlotFilter();
	private vtkExtractEdges linePlotEE_ = new vtkExtractEdges();
	private vtkPolyDataMapper facesM_ = new vtkPolyDataMapper();
	private vtkPolyDataMapper outlinesM_ = new vtkPolyDataMapper();
	private double linePlotScale_ = 1;
	private boolean scalarRangeDirty_;
	private vtkScalarBarActor scalarBarA_ = ScalarBarFactory.createScalarBar();
	private vtkLookupTable lookupTable_ = new vtkLookupTable();
	private ExtractMinMaxFilter minMaxFilter_ = new ExtractMinMaxFilter();
	private IDLabelsHelper idLabelsHelper_ = new IDLabelsHelper();

	public LinePlotPipeline() {
		vtkActor facesA = new vtkActor();
		vtkActor outlinesA = new vtkActor();

		// listen to changes
		addPropertyChangeListener(createListener());

		// settings
		updatePipeline();

		// min/max
		minMaxFilter_.SetInput(linePlotLPF_.GetPolyDataOutput());
		idLabelsHelper_.setInput(minMaxFilter_.GetPolyDataOutput());

		// scalar bar
		scalarBarA_.SetLookupTable(lookupTable_);

		// faces
		facesM_.SetLookupTable(lookupTable_);
		facesM_.SetInput(linePlotLPF_.GetPolyDataOutput());
		facesA.SetMapper(facesM_);

		// outlines
		linePlotEE_.SetInput(linePlotLPF_.GetPolyDataOutput());
		outlinesM_.SetInput(linePlotEE_.GetOutput());
		outlinesA.SetMapper(outlinesM_);

		// actors
		registerAM(ACTOR_FACES, new ActorManager(facesA) {

			@Override
			public boolean canRender() {
				return getDataProvider().providesData();
			}
		});
		registerAM(ACTOR_OUTLINES, new ActorManager(outlinesA) {

			@Override
			public boolean canRender() {
				return getDataProvider().providesData();
			}
		});
		registerAM(ACTOR_SCALAR_BAR, new ActorManager(scalarBarA_) {

			@Override
			public boolean canRender() {
				return getDataProvider().providesData();
			}
		});
		registerAM(ACTOR_MIN_MAX_POINTS, new ActorManager(idLabelsHelper_
				.getPointsActor()) {

			@Override
			public boolean canRender() {
				return getDataProvider().providesData();
			}
		});
		registerAM(ACTOR_MIN_MAX_LABELS, new ActorManager(idLabelsHelper_
				.getLabelsActor()) {

			@Override
			public boolean canRender() {
				return getDataProvider().providesData();
			}
		});

		// reset at the end
		reset();
	}

	private PropertyChangeListener createListener() {
		return new PropertyChangeListener() {

			public void propertyChange(PropertyChangeEvent evt) {
				if (Pipeline.PROP_MODEL.equals(evt.getPropertyName())) {
					scalarRangeDirty_ = true;
					updateLinePlotScale();
				}
				if (ElementsDP.PROP_DATA_NAME.equals(evt.getPropertyName())) {
					scalarRangeDirty_ = true;
				}
			}
		};
	}

	@Override
	protected Class<? extends DataProvider> getAdapterType() {
		return LinePlotDP.class;
	}

	public String[] getAvailableFields() {
		return getCastedDataProvider().getAvailableFields();
	}

	private LinePlotDP getCastedDataProvider() {
		return (LinePlotDP) getDataProvider();
	}

	@Override
	protected DataProvider getDefaultDataProvider() {
		return new DDP();
	}

	public String getField() {
		return getCastedDataProvider().getField();
	}

	public int getOrientation() {
		return linePlotLPF_.getOrientation();
	}

	public int getResolution() {
		return getCastedDataProvider().getResolution();
	}

	public double getScale() {
		return linePlotScale_;
	}

	public boolean isUseNormal() {
		return linePlotLPF_.isUseNormal();
	}

	public void preRender() {
		if (scalarRangeDirty_ && getDataProvider().providesData()) {
			double[] r = getCastedDataProvider().getScalarRange();

			lookupTable_.SetRange(r);
			lookupTable_.Build();

			linePlotLPF_.setScalarRange(r);
			facesM_.SetScalarRange(r);
			scalarRangeDirty_ = false;
		}
	}

	public void reset() {
		setHueRange(new double[] { 2.0 / 3.0, 0 });
		linePlotLPF_.setScaleToScalarRange(true);
		idLabelsHelper_.getLabelTextProperty().SetColor(0, 0, 0);
		idLabelsHelper_.getPointsActor().GetProperty().SetColor(0, 0, 0);

		getActorManager(ACTOR_FACES).getCastedActor().GetProperty().SetOpacity(
				0.8);
		outlinesM_.ScalarVisibilityOff();
		getActorManager(ACTOR_OUTLINES).getCastedActor().GetProperty()
				.SetColor(0, 0, 0);
	}

	public void setField(String field) {
		getCastedDataProvider().setField(field);
		scalarBarA_.SetTitle(field);
		scalarRangeDirty_ = true;
	}

	public void setHueRange(double[] hr) {
		lookupTable_.SetHueRange(hr);
	}

	public void setOrientation(int orientation) {
		linePlotLPF_.setOrientation(orientation);
	}

	public void setResolution(int r) {
		getCastedDataProvider().setResolution(r);
	}

	public void setScale(double scale) {
		linePlotScale_ = scale;
		updateLinePlotScale();
	}

	public void setUseNormal(boolean useNormal) {
		linePlotLPF_.setUseNormal(useNormal);
	}

	private void updateLinePlotScale() {
		double modelSize = SymbolsPipeline.getSize(getDataProvider()
				.getModelBounds());

		linePlotLPF_
				.setScale(-(MODEL_SCALE_FACTOR * linePlotScale_ * modelSize));
	}

	@Override
	protected void updatePipeline() {
		linePlotLPF_.SetInput(getCastedDataProvider().getPolyData());
	}
}
